﻿/**************************************************************
 *
 * 唯一标识：a99a76f5-f40b-4edd-8169-7181108691ad
 * 命名空间：Microsoft.Extensions.DependencyInjection
 * 创建时间：2023/8/21 11:39:17
 * 机器名称：DESKTOP-S0D075D
 * 创建者：antho
 * 电子邮箱：fengqinhua2016@163.com
 * 描述：
 *
 **************************************************************/

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using Sgr.Identity;
using Sgr.Identity.Services;
using Sgr.Utilities;
using System.Text;
using System.Text.Json;

namespace Microsoft.Extensions.DependencyInjection
{
    /// <summary>
    /// 扩展
    /// </summary>
    public static class ServiceCollectionExtensions
    {
        /// <summary>
        /// 添加 SGR 认证核心服务
        /// </summary>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
        /// <param name="useJwt"></param>
        /// <param name="useCookie"></param>
        /// <returns></returns>
        public static IServiceCollection AddSgrAuthentication(this IServiceCollection services,
            IConfiguration configuration,
            bool useJwt = true,
            bool useCookie = false)
        {
            // 注册身份认证选项
            ConfigureIdentityOptions(services, configuration);

            // 根据配置添加认证
            services.AddAuthentication(options =>
            {
                options.DefaultScheme = useJwt ? JwtBearerDefaults.AuthenticationScheme
                                             : CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = options.DefaultScheme;
            });

            if (useJwt)
                services.AddSgrJwtAuthentication();

            if (useCookie)
                services.AddSgrCookieAuthentication();

            // 注册其他依赖服务
            services.TryAddScoped<CustomCookieAuthenticationEvents>();

            return services;
        }

        /// <summary>
        /// 配置身份认证选项
        /// </summary>
        private static void ConfigureIdentityOptions(IServiceCollection services, IConfiguration configuration)
        {
            services.AddOptions<IdentityOptions>()
                .Bind(configuration.GetSection("Sgr:Identity"))
                .PostConfigure(options =>
                {
                    // 设置默认值
                    options.JWT ??= JwtOptions.CreateDefault();
                    options.Cookie ??= HttpCookieOptions.CreateDefault();

                    // 从环境变量获取JWT密钥
                    options.JWT.Key = GetJwtKey();
                })
                .Validate(options =>
                {
                    // 验证必要的配置项
                    if (string.IsNullOrEmpty(options.JWT?.Issuer))
                        throw new InvalidOperationException("JWT Issuer must be configured.");
                    if (string.IsNullOrEmpty(options.JWT?.Audience))
                        throw new InvalidOperationException("JWT Audience must be configured.");
                    return true;
                });

            services.AddOptions<CaptchaOptions>()
                .Bind(configuration.GetSection("Sgr:Captcha"));
        }

        /// <summary>
        /// 获取JWT密钥
        /// </summary>
        private static string GetJwtKey()
        {
            const string ENV_JWT_KEY = "SGR_JWT_KEY";
            return EnvironmentHelper.GetEnvironmentVariable(ENV_JWT_KEY)
                ?? Guid.NewGuid().ToString("D").ToUpper();
        }

        /// <summary>
        /// 添加JWT认证
        /// </summary>
        private static IServiceCollection AddSgrJwtAuthentication(this IServiceCollection services)
        {
            services.AddOptions<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme)
                .Configure<IOptions<IdentityOptions>>((options, identityOptions) =>
                {
                    var jwtOptions = identityOptions.Value.JWT;
                    ConfigureJwtBearerOptions(options, jwtOptions);
                });

            services.AddAuthentication().AddJwtBearer();

            return services;
        }

        /// <summary>
        /// 配置JWT Bearer选项
        /// </summary>
        private static void ConfigureJwtBearerOptions(JwtBearerOptions options, JwtOptions jwtOptions)
        {
            // 禁用默认的声明映射，使用原始声明类型
            options.MapInboundClaims = false;

            // 配置Token验证参数
            options.TokenValidationParameters = CreateTokenValidationParameters(jwtOptions);

            // 配置认证事件
            options.Events = CreateJwtBearerEvents();
        }

        /// <summary>
        /// 创建Token验证参数
        /// </summary>
        private static TokenValidationParameters CreateTokenValidationParameters(JwtOptions jwtOptions)
            => new()
            {
                // 验证选项
                ValidateIssuer = true,             // 验证发行者
                ValidateAudience = true,           // 验证接收者
                ValidateLifetime = true,           // 验证过期时间
                ValidateIssuerSigningKey = true,   // 验证签名
                RequireExpirationTime = true,      // 要求必须包含过期时间
                RequireSignedTokens = true,        // 要求必须签名

                // 有效值设置
                ValidIssuer = jwtOptions.Issuer,
                ValidAudience = jwtOptions.Audience,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.Key)),

                // 过期时间容错值，解决服务器端时间不同步问题（秒）
                ClockSkew = TimeSpan.FromSeconds(jwtOptions.ClockSkewSeconds)
            };

        /// <summary>
        /// 创建JWT事件处理
        ///    请求 -> JWT中间件
        ///    ├── 有令牌但验证失败 -> OnAuthenticationFailed
        ///    ├── 需要令牌但未提供 -> OnChallenge
        ///    └── 验证成功 -> 继续处理请求
        /// </summary>
        private static JwtBearerEvents CreateJwtBearerEvents()
            => new()
            {
                OnTokenValidated = ValidateToken,
                // OnAuthenticationFailed 在令牌验证过程中发生异常时立即触发
                OnAuthenticationFailed = context =>
                {
                    string tokenError = context.Exception switch
                    {
                        SecurityTokenExpiredException => TokenErrorType.Expired,
                        SecurityTokenInvalidSignatureException => TokenErrorType.InvalidSignature,
                        SecurityTokenInvalidIssuerException => TokenErrorType.InvalidIssuer,
                        _ => TokenErrorType.Invalid
                    };

                    context.HttpContext.SetTokenError(tokenError);

                    //// 可选：记录详细的异常信息用于调试
                    //if (context.Exception != null)
                    //{
                    //    context.HttpContext.Items["Token-Exception"] = context.Exception.Message;
                    //}

                    return Task.CompletedTask;
                },
                // OnChallenge 在访问需要授权的资源但未提供有效令牌时触发.例如：在 [Authorize] 特性验证失败时触发
                OnChallenge = HandleUnauthorizedChallenge
            };

        /// <summary>
        /// 处理未授权响应
        /// </summary>
        private static Task HandleUnauthorizedChallenge(JwtBearerChallengeContext context)
        {
            //此处代码为终止.Net Core默认的返回类型和数据结果，这个很重要哦，必须
            context.HandleResponse();

            // 设置响应标头
            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
            context.Response.ContentType = "application/problem+json";

            // 添加 WWW-Authenticate 头部（RFC 7235 标准）
            context.Response.Headers.Append(
                "WWW-Authenticate",
                BuildAuthenticateHeader(context));

            var problemDetails = context.HttpContext.CreateUnauthorizedProblemDetails();
            return context.Response.WriteAsJsonAsync(problemDetails, JsonHelper.GetJsonSerializerOptions());
        }

        //private static readonly JsonSerializerOptions JsonOptions = new()
        //{
        //    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        //    WriteIndented = false
        //};

        private static async Task ValidateToken(TokenValidatedContext context)
        {
            try
            {
                var jti = context.Principal?.Claims?
                    .FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Jti)?.Value;

                if (string.IsNullOrEmpty(jti))
                {
                    context.HttpContext.SetTokenError(TokenErrorType.Invalid);
                    context.Fail("令牌ID (jti) 不能为空");
                    return;
                }

                var tokenBlacklistService = context.HttpContext.RequestServices
                    .GetService<ITokenBlacklistService>();

                if (tokenBlacklistService == null)
                    return;

                if (await tokenBlacklistService.IsBlacklistedAsync(jti))
                {
                    // 设置令牌错误类型为已列入黑名单
                    context.HttpContext.SetTokenError(TokenErrorType.Blacklisted);

                    // 记录额外的上下文信息用于调试
                    context.HttpContext.Items["Token-JTI"] = jti;

                    context.Fail("Invalid or blacklisted token");
                }
            }
            catch (Exception ex)
            {
                // 记录具体的验证错误
                context.HttpContext.SetTokenError(TokenErrorType.Invalid);
                context.Fail($"令牌验证失败: {ex.Message}");
            }
        }

        private static string BuildAuthenticateHeader(JwtBearerChallengeContext context)
        {
            var header = new StringBuilder("Bearer");
            if (!string.IsNullOrEmpty(context.Error))
                header.Append($" error=\"{context.Error}\"");
            if (!string.IsNullOrEmpty(context.ErrorDescription))
                header.Append($" error_description=\"{context.ErrorDescription}\"");
            return header.ToString();
        }

        /// <summary>
        /// 添加Cookie认证
        /// </summary>
        private static IServiceCollection AddSgrCookieAuthentication(this IServiceCollection services)
        {
            if (!services.Any(sd =>
                    sd.ServiceType == typeof(IOptions<CookieAuthenticationOptions>) ||
                    sd.ServiceType == typeof(IOptionsSnapshot<CookieAuthenticationOptions>) ||
                    sd.ServiceType == typeof(IOptionsMonitor<CookieAuthenticationOptions>)))
            {
                services.AddOptions<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme)
                    .Configure<IOptions<IdentityOptions>>((options, identityOptions) =>
                    {
                        var cookieOptions = identityOptions.Value.Cookie;
                        ConfigureCookieAuthenticationOptions(options, cookieOptions);
                    });
            }

            services.AddAuthentication()
                .AddCookie();

            return services;
        }

        /// <summary>
        /// 配置Cookie认证选项
        /// </summary>
        private static void ConfigureCookieAuthenticationOptions(
            CookieAuthenticationOptions options,
            HttpCookieOptions cookieOpt)
        {
            //设置存储用户登录信息（用户Token信息）的Cookie名称
            options.Cookie.Name = cookieOpt.Name;
            //设置存储用户登录信息（用户Token信息）的Cookie，无法通过客户端浏览器脚本(如JavaScript等)访问到
            options.Cookie.HttpOnly = cookieOpt.HttpOnly;
            // 过期时间
            options.ExpireTimeSpan = TimeSpan.FromSeconds(cookieOpt.ExpireSeconds);
            //是否在过期时间过半的时候，自动延期
            options.SlidingExpiration = cookieOpt.SlidingExpiration;
            //认证失败，会自动跳转到这个地址
            options.LoginPath = cookieOpt.LoginPath;
            //注销路径
            if (!string.IsNullOrEmpty(cookieOpt.LogoutPath))
                options.LogoutPath = cookieOpt.LogoutPath;
            //访问拒绝页路径
            if (!string.IsNullOrEmpty(cookieOpt.AccessDeniedPath))
                options.AccessDeniedPath = cookieOpt.AccessDeniedPath;
            //
            if (!string.IsNullOrEmpty(cookieOpt.ReturnUrlParameter))
                options.ReturnUrlParameter = cookieOpt.ReturnUrlParameter;

            //// Secure 属性，确保通过 HTTPS 发送
            options.Cookie.SecurePolicy = HttpCookieOptions.ToCookieSecurePolicy(cookieOpt.SecurePolicy);

            //SameSite 策略
            options.Cookie.SameSite = HttpCookieOptions.ToSameSiteMode(cookieOpt.SameSite);

            //自定义事件处理程序
            options.EventsType = typeof(CustomCookieAuthenticationEvents);
            //options.Events = new CookieAuthenticationEvents
            //{
            //    OnRedirectToLogin = context =>
            //    {
            //    }
            //};
        }
    }
}